[AD] Scalaアプリケーションの開発・保守は合同会社ミルクソフトにお任せください
この記事では、Scala 3(コードネーム:Dotty)で新たに導入された「合併型(Union Types)」について解説します。
合併型とは「AまたはB」を表す型
合併型とは、二つの型の性質のいずれかを満たすような値のもつ型を表します。
|記号で表します。
例えばAという型とBという型があるとき、A | Bと表すと「Aである、もしくはBである型」という意味になります。
例えば以下のようなコードを書くことができます。
case class UserId(value: Int) extends AnyVal case class Email(value: String) extends AnyVal case class User() def findUser(id: UserId | Email): User = { id match { case UserId(value) => lookupById(value) case Email(value) => lookupByEmail(value) } }
合併型は可換的で結合的
合併型は可換的です。
つまり、A | B は B | A と同じ型となります。
また、結合的でもあります。
つまり、A | (B | C) は (A | B) | Cと同じ型となります。
合併型は交差型と組み合わせると分配的
交差型は&記号を使用し、例えば型Aと型Bについて「AかつB」といった関係性を表現します。
合併型は交差型と組み合わせることにより、分配的になります。
A & (B | C) は、A & B | A & Cと同じ型となります。
計算の優先順位は&の方が高く、|の方が低いので、このように表記することができます。
合併型は交差型と対をなす概念なので、交差型についても合わせて押さえておくと良いでしょう。
内部構造までは引き継がない
以下のコードはコンパイルが通りません。
というのも、A | Bにはdef run: Resultというメソッドを持たないからです。
trait A { def run: Result } trait B { def run: Result } def execute(x: A | B) = x.run
たとえ両者に共通するメンバが存在していたとしても、合併した型にこれが引き継がれることはないということです。
これに対して、以下のコンパイルは通ります。
trait C { def run: Result } trait A extends C trait B extends C def execute(x: A | B) = x.run
AとBはいずれもCのサブクラスなので、A | BもCのサブクラスです。
したがってrunメソッドを持っている、というわけです。
備考
本記事では"union types"に対する訳語として「合併型」を採用しています。
「直和型」「合併型」の違いについては以下のリンクを参考としています。